Skip to content

Add SwiftUI hosting smoke tests for field-level Observation - #22

Open
thliu21 wants to merge 1 commit into
sonmbol:mainfrom
thliu21:agent/kmpobservablebridge-18
Open

Add SwiftUI hosting smoke tests for field-level Observation#22
thliu21 wants to merge 1 commit into
sonmbol:mainfrom
thliu21:agent/kmpobservablebridge-18

Conversation

@thliu21

@thliu21 thliu21 commented Jul 31, 2026

Copy link
Copy Markdown

Summary

  • add Apple-platform SwiftUI hosting smoke tests for two independent projected fields
  • verify a field B emission does not reevaluate the field A-only subtree on Observation-capable systems
  • verify global invalidation and the ObservableObject fallback both invalidate the expected hosted subtrees
  • make view identity, shared observation setup, and teardown cancellation explicit
  • document supported Xcode versions and simulator destinations

Fixes #18

Harness

Each projected field is rendered by a separate SwiftUI View identity. The
runtime path uses KMPObservedObject, while a test-only configuration forces
the same production store through its ObservableObject fallback.

The source emits dependency notifications synchronously. Tests wait for
positive body-render and cancellation signals; timeouts are failure bounds
only. The field-isolation assertion is made after the B subtree has rendered,
without an inverted expectation or sleep.

The suite also includes a runtime-gated iOS 15/16 fallback test. A forced
fallback test keeps that contract covered on current runtimes when an older
simulator is unavailable.

Validation

  • SwiftUI hosting suite: 3 passed, 1 expected runtime skip, 0 failures
  • repeated hosting run: 3 passed, 1 expected runtime skip, 0 failures
  • complete strict-concurrency compilation
  • API compatibility: no breaking changes in all three products
  • package manifest consistency
  • whitespace validation

The repository-wide -warnings-as-errors command currently stops on three
pre-existing Swift 6.3 WeakMutability diagnostics in
KMPObservableBridgeTests.swift; the new hosting test file compiles without
diagnostics.

Verified locally with Xcode 26.5 on macOS 26.5.2. An iOS 15/16 runtime was not
installed locally, so that runtime-specific case remains explicitly
unverified rather than being reported as passed.

@sonmbol sonmbol left a comment

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Thank you for identifying and covering this important gap. Exercising field-level dependency behavior through a real SwiftUI hosting graph adds valuable protection beyond the existing registrar-level tests. The direction is strong, and the production code remains untouched.

I found a few test-correctness and organization details that should be addressed before merge. They are noted inline below.

timeout: 2
)

guard supportsFieldLevelObservation else {

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you please use the same verified teardown path before skipping? The hierarchy has already mounted three observations, but this branch removes content without waiting for cancellation. This keeps legacy-runtime tests isolated and proves deterministic teardown on every mounted path.

guard supportsFieldLevelObservation else {
    await removeContent(
        from: harness,
        assertingTeardownOf: model
    )
    throw XCTSkip(
        "Field isolation requires iOS 17+ or macOS 14+."
    )
}


model.second.emit(1)

await fulfillment(of: [secondRendered], timeout: 2)

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you add a deterministic render-queue fence before asserting that the first subtree stayed unchanged? Waiting for the second render proves the positive event, but a mistakenly scheduled global invalidation could still be queued for the next main-loop turn, allowing a false pass. A harness method that flushes layout across a main-queue boundary would make the negative assertion reliable. A strong [self] capture is safe and preferable here because the queue releases the closure after execution and the harness must remain alive until the flush completes.

await fulfillment(of: [secondRendered], timeout: 2)
await harness.flushPendingUpdates()
XCTAssertEqual(probe.renderCount(for: .first), firstBaseline)

}
}

private final class HostingSignal:

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Could you split this 575-line file by responsibility? It currently combines test cases, observation fixtures, render probes/views, and UIKit/AppKit hosting. Keeping these in SwiftUIHostingTests, HostingObservationFixture, HostingRenderViews, and AppleHostingHarness files will make future lifecycle and rendering changes much safer to review, without changing runtime behavior.

return
}
stoppedCount += 1
onStop?()

Copy link
Copy Markdown
Owner

Choose a reason for hiding this comment

The reason will be displayed to describe this comment to others. Learn more.

Please consume this test callback once so the signal releases the captured expectation immediately and cannot over-fulfill it if the fixture later supports restarting observation.

let action = onStop
onStop = nil
action?()

Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment

Labels

None yet

Projects

None yet

Development

Successfully merging this pull request may close these issues.

Add SwiftUI hosting smoke tests for field-level Observation

2 participants